<template>
  <div class="common-layout">
    <el-container>
      <el-aside width="200px">
        <el-button @click="OpenForm">新建</el-button>
        <el-input v-model="search" class="table_search" placeholder="搜索命令"/>
        <ul>
          <li v-for="c in commands">
            <el-button link type="success" @click="SendMsg(c)">{{c}}</el-button>
          </li>
        </ul>
      </el-aside>

      <el-main>
        <el-tabs v-model="editTabsValue"
                 closable @tab-remove="RemoveTab" @tab-change="TabChange">
          <el-tab-pane v-for="item in editTabs"
                       :name="item.name"
                       :label="item.name"
                       :key="item.name">
            <div class="ssh-container" :name="item.attr"></div>
          </el-tab-pane>
        </el-tabs>
      </el-main>
    </el-container>
  </div>

  <el-dialog v-model="TerminalDialog" style="width: 22vw">
    <el-form ref="form" :model="form" status-icon :rules="rules" label-position="left" label-width="80px">
      <el-form-item label="IP地址" prop="ip">
        <el-input v-model="form.ip"/>
      </el-form-item>
      <el-form-item label="端口" prop="port">
        <el-input v-model="form.port"/>
      </el-form-item>
      <el-form-item label="用户名" prop="user">
        <el-input v-model="form.user"/>
      </el-form-item>
      <el-form-item label="密码" prop="pwd">
        <el-input v-model="form.pwd" type="password"/>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="submitForm('form')">连接</el-button>
        <el-button @click="resetForm('form')">重置</el-button>
      </el-form-item>
    </el-form>
  </el-dialog>

</template>

<script lang="ts">
import {Terminal} from 'xterm'
import {FitAddon} from 'xterm-addon-fit'
import 'xterm/css/xterm.css'
import {debounce} from 'lodash'
import {GetUniqueID} from '@/utils/common'
import {Search} from '@element-plus/icons-vue'

const packResize = (cols, rows) =>
    JSON.stringify({
      type: 'resize',
      cols: cols,
      rows: rows
    })


export default {
  name: 'MyTerminal',
  created() {
    this.form.ip = this.$route.query.ip || '10.1.1.26'
    this.form.port = this.$route.query.port || '22'
    this.form.user = this.$route.query.port || 'root'
  },
  data() {
    var validate = (rule, value, callback) => {
      if (value === '') {
        callback(new Error('不能为空'));
      } else {
        callback();
      }
    };
    return {
      rules: {
        ip: [
          {validator: validate, trigger: 'blur'}
        ],
        port: [
          {validator: validate, trigger: 'blur'}
        ],
        user: [
          {validator: validate, trigger: 'blur'}
        ],
        pwd: [
          {validator: validate, trigger: 'blur'}
        ],
      },
      form: {
        user: '',
        pwd: '',
        ip: '',
        port: '',
      },
      search:"",
      commands: [],
      command: "",
      cur_tab: Object,
      TerminalDialog: false,
      activeName: '',
      initText: '连接中...\r\n',
      first: true,
      term: null,
      stdin:"",
      stdout:"",
      fitAddon: null,
      ws: null,
      ws_dict: {},
      term_dict: {},
      socketUrl: 'ws://localhost:8080/ws',
      option: {
        lineHeight: 1.0,
        cursorBlink: true,
        cursorStyle: 'block', // 光标样式 'block' | 'underline' | 'bar'
        fontSize: 14,
        fontFamily: "Monaco, Menlo, Consolas, 'Courier New', monospace",
        theme: {
          background: '#181d28'
        },
        cols: 30 // 初始化的时候不要设置fit，设置col为较小值（最小为可展示initText初始文字即可）方便屏幕缩放
      },

      editTabs: [],
      tabs: [],
      editTabsValue: ""
    }
  },
  beforeDestroy() {
    this.removeResizeListener()
    this.term && this.term.dispose()
  },
  methods: {
    OpenForm() {
      this.TerminalDialog = true
    },
    RemoveTab(targetName) {
      let tabs = this.editTabs
      let activeName = this.editTabsValue
      if (activeName == targetName) {
        tabs.forEach((tab, index) => {
          if (tab.name == targetName) {
            let nextTab = tabs[index + 1] || tab[index - 1]
            if (nextTab) {
              activeName = nextTab.name
            }
          }
        })
      }
    },
    TabChange(TabPaneName) {
      console.log(TabPaneName)
      this.editTabs.forEach((tab, index) => {
        if (tab.name == TabPaneName) {
          console.log(tab)
          this.cur_tab = tab
          this.ws = this.ws_dict[tab.attr]
          this.term = this.term_dict[tab.attr]
        }
      })
      this.activeName = TabPaneName
      this.editTabsValue = TabPaneName
    },
    initWs() {
      this.initTerm()
      this.initSocket()
      this.onTerminalResize()
      this.onTerminalKeyPress()
    },
    submitForm(formName) {
      this.$refs[formName].validate((valid) => {
        if (valid) {
          let id = GetUniqueID()
          this.cur_tab = {name: this.form.ip, attr: id}
          this.editTabs.push(this.cur_tab)
          this.activeName = this.form.ip
          this.editTabsValue = this.form.ip
          this.initWs()
          this.TerminalDialog = false
        } else {
          return false;
        }
      });
    },
    resetForm(formName) {
      this.$refs[formName].resetFields();
    },
    handleClick(tab, event) {
      if (tab.name === 'second') {
        // this.init()
      }
    },
    utf8_to_b64(rawString) {
      return btoa(unescape(encodeURIComponent(rawString)));
    },
    b64_to_utf8(encodeString) {
      return decodeURIComponent(escape(atob(encodeString)));
    },
    bytesHuman(bytes, precision) {
      if (!/^([-+])?|(\.\d+)(\d+(\.\d+)?|(\d+\.)|Infinity)$/.test(bytes)) {
        return '-'
      }
      if (bytes === 0) return '0';
      if (typeof precision === 'undefined') precision = 1;
      const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB', 'BB'];
      const num = Math.floor(Math.log(bytes) / Math.log(1024));
      const value = (bytes / Math.pow(1024, Math.floor(num))).toFixed(precision);
      return `${value} ${units[num]}`
    },
    isWsOpen() {
      return this.ws && this.ws.readyState === 1
    },
    async initTerm() {
      this.term = new Terminal(this.option)
      this.fitAddon = new FitAddon()
      this.term.loadAddon(this.fitAddon)
      let div = await document.getElementsByName(this.cur_tab.attr)
      if (div.length == 0) {
        return
      }
      this.term.open(div[0])
      // this.fitAddon.fit() // 初始化的时候不要使用fit
      setTimeout(() => {
        this.fitAddon.fit()
      }, 500); // 必须延时处理
    },
    SendMsg(msg){
      this.isWsOpen() && this.ws.send(JSON.stringify({
        type: 'stdin',
        data: this.utf8_to_b64(msg)
      }))
    },
    onTerminalKeyPress() {
      this.term.onData(data => {
        this.command = this.command + data
        this.stdin = data
        this.isWsOpen() && this.ws.send(JSON.stringify({
          type: 'stdin',
          data: this.utf8_to_b64(data)
        }))
      })
    },
    // resize 相关
    resizeRemoteTerminal() {
      const {cols, rows} = this.term
      // 调整后端终端大小 使后端与前端终端大小一致
      this.isWsOpen() && this.ws.send(packResize(cols, rows))
    },
    onResize: debounce(function () {
      this.fitAddon.fit()
    }, 500),
    onTerminalResize() {
      window.addEventListener('resize', this.onResize)
      this.term.onResize(this.resizeRemoteTerminal)
    },
    removeResizeListener() {
      window.removeEventListener('resize', this.onResize)
    },
    // socket
    initSocket() {
      this.term.write(this.initText)
      this.ws = new WebSocket(this.socketUrl, ['webssh'])
      this.term_dict[this.cur_tab.attr] = this.term
      this.ws_dict[this.cur_tab.attr] = this.ws
      this.onOpenSocket()
      this.onCloseSocket()
      this.onErrorSocket()
      this.term._initialized = true
      this.onMessageSocket()
    },
    onOpenSocket() {
      const ws = this.ws_dict[this.cur_tab.attr]
      const term = this.term_dict[this.cur_tab.attr]
      ws.onopen = () => {
        ws.send(JSON.stringify({type: "addr", data: this.utf8_to_b64(this.form.ip + ':' + this.form.port)}));
        ws.send(JSON.stringify({type: "login", data: this.utf8_to_b64(this.form.user)}));
        ws.send(JSON.stringify({type: "password", data: this.utf8_to_b64(this.form.pwd)}));
        term.reset()
        setTimeout(() => {
          this.resizeRemoteTerminal()
        }, 500)
      }
    },
    onCloseSocket() {
      this.ws.onclose = () => {
        const term = this.term_dict[this.cur_tab.attr]
        term.write("未连接， 刷新后重连...\r\n");
        setTimeout(() => {
          this.initSocket();
        }, 3000)
      }
    },
    onErrorSocket() {
      this.ws.onerror = () => {
        const term = this.term_dict[this.cur_tab.attr]
        term.write('连接失败，请刷新！\n')
      }
    },
    onMessageSocket() {
      const ws = this.ws_dict[this.cur_tab.attr]
      const term = this.term_dict[this.cur_tab.attr]

      ws.onmessage = res => {
        const msg = JSON.parse(res.data)
        // 第一次连接成功将 initText 清空
        if (this.first) {
          this.first = false
          term.reset()
          term.element && term.focus()
          this.resizeRemoteTerminal()
        }
        if (msg.data == "DQo="){
          console.log(this.stdin,this.command)
          this.commands.push(this.command)
          this.command = ""
        }
        term.write(this.b64_to_utf8(msg.data))
      }
    }
  }
}
//
</script>
<style lang="scss" scoped>
.el-aside {
  padding-left: 10px;
}
ul {
  height: 90%;
}
.ssh-container {
  overflow: hidden;
  height: 85vh;
  border-radius: 4px;
  background: rgb(24, 29, 40);
  padding: 0px;
  color: rgb(255, 255, 255);

  .xterm-scroll-area::-webkit-scrollbar-thumb {
    background-color: #b7c4d1;
    /* 滚动条的背景颜色 */
  }
}
@import "@/styles/webkit.scss";

.el-aside .el-tree{
  height: 80%;
  max-height: 94%;
  width: 198px;
}
.el-aside {
  width: 200px;
  border-right: 1px solid #a9a4a4;
}
.tree_operation {
  width: 100%;
}

.tree_operation:hover .tree_operation_list{
  display: block;
  float: right;

}
.tree_operation .el-icon {
  margin-right: 3px;
}


.tree_operation_list {
  display: none;
  float: right;
}

.tree_operation_list:hover {
  background-color: #b0a9a9;
  border-radius: 10px;
  text-align: center;
}

.tree_operation_list > .el-dropdown {
  margin-left: 25px;
}


.tree_operation_list > .el-dropdown:first-child {
  margin-left: 15px;
  color:green;
  border: none;
}

.el-icon{
  border: none;
}

.morefilled:hover {
  border: none;
}

.ssh-container {
  border: 1px darkgray solid;
}
</style>
